package de.sendorian.app.forumArchive.finder;

import static org.apache.commons.lang.StringEscapeUtils.unescapeHtml;
import static org.apache.commons.lang.StringUtils.trim;
import static org.apache.commons.lang.StringUtils.trimToNull;
import static org.hamcrest.Matchers.anything;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.concurrent.NotThreadSafe;

import org.apache.log4j.Logger;
import org.hamcrest.Matcher;
import org.htmlparser.Node;
import org.htmlparser.filters.TagNameFilter;
import org.htmlparser.nodes.TagNode;
import org.htmlparser.util.NodeList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import de.sendorian.app.forumArchive.domain.Forum;
import de.sendorian.app.forumArchive.domain.Topic;
import de.sendorian.app.forumArchive.domain.User;
import de.sendorian.app.forumArchive.repository.UserRepository;


/**
 * Finds all the topics for the forum. Optionally a filter can be provided to
 * take influence which forums should be returned.
 * 
 * This class is NOT safe for multithreading so be sure to create one instance
 * for each forum in a threaded environment!
 * 
 * @author sendorian
 * 
 */
@NotThreadSafe
@Component
public abstract class TopicFinder extends FinderBase<Forum, Topic> {

    private static final Logger LOG = Logger.getLogger(TopicFinder.class);

    protected Matcher<Topic> nonCriticalfilter = anything();

    protected PostFinder postFinder;
    
    protected UserRepository userRepository;

    int pageNr = 0;

    boolean tryAgain = true;


    public TopicFinder() {
    }


    public TopicFinder(Matcher<Topic> filter, boolean tryAgain) {
        super(filter);
        this.tryAgain = tryAgain;
    }


    public TopicFinder(Matcher<Topic> filter, Matcher<Topic> nonCriticalfilter, boolean tryAgain) {
        super(filter);
        this.tryAgain = tryAgain;
        this.nonCriticalfilter = nonCriticalfilter;
    }


    public List<Topic> find(Forum forum) {
        pageNr = 0;
        List<Topic> topics = find(forum, forum.getUrl());
        forum.setPages(pageNr);
        LOG.info("Found " + topics.size() + " topics in '" + forum.getName() + "'");
        return topics;
    }


    public List<Topic> find(Forum forum, String url) {
        List<Topic> topics = new ArrayList<Topic>();
        NodeList tableRows = parse(url, new TagNameFilter("tr"), false);
        if (tableRows == null)
            return topics;

        pageNr++;
        LOG.debug("Found topics in " + forum.getName() + ", Page " + pageNr + ":");
        for (Node node : tableRows.toNodeArray()) {
            Topic topic = new Topic();
            TagNode row = (TagNode) node;
            String link = getLinkWithoutAnchor(row);
            if (link != null) {
                topic.setUrl(link);
                topic.setLastPost(getLastPost(row));
                topic.setName(trim(unescapeHtml(getTopicName(row))));
                topic.setType(getType(row));
                String userName = trimToNull(unescapeHtml(getTopicStarter(row)));
                if (userName != null) {
                    User user = userRepository.findByName(userName);
                    if (user == null) {
                        user = new User(userName);
                        userRepository.save(user);
                    }
                    topic.setTopicStarter(user);
                }
                topic.setPosts(postFinder.find(topic));

                if (nonCriticalfilter.matches(topic)) {
                    if (filter.matches(topic)) {
                        topics.add(topic);
                        LOG.debug("Adding " + topic.toString());
                    } else {
                        if (!tryAgain) {
                            LOG.debug("Filter not matching any more. Returning.");
                            return topics;
                        }
                    }
                }
            }
        }

        String nextPage = getNextPage(url, pageNr);
        if (nextPage != null) {
            List<Topic> nextPosts = find(forum, nextPage);
            topics.addAll(nextPosts);
        }

        return topics;
    }


    /**
     * Calls {@link #getLink(TagNode)} and removes an anchor ("#anchor") from
     * the end of the link if present.
     */
    private String getLinkWithoutAnchor(TagNode row) {
        String link = getLink(row);
        if (link == null)
            return null;

        int anchorTagPos = link.lastIndexOf('#');
        if (anchorTagPos != -1) {
            link = link.substring(0, anchorTagPos);
        }
        return link;
    }


    protected abstract String getTopicName(TagNode row);


    protected abstract String getTopicStarter(TagNode row);


    protected abstract Date getLastPost(TagNode row);


    protected abstract String getLink(TagNode row);


    protected abstract Topic.Type getType(TagNode row);


    abstract protected String getNextPage(String url, int actualPageNr);
    
    
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    
    @Autowired
    public void setPostFinder(PostFinder postFinder) {
        this.postFinder = postFinder;
    }
}

